package java_thinking.unit_17;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * @Desc:   LRU缓存
 *      1.什么是LRU：
 *          LRU:Least Recently Used 即最近最久未使用
 *      2.LRU缓存的思想:
 *          2.1 固定缓存大小，需要给缓存分配一个固定的大小。
 *          2.2 每次读取缓存都会改变缓存的使用时间，将缓存的存在时间重新刷新。
 *          2.3 需要在缓存满了后，将最近最久未使用的缓存删除，再添加最新的缓存
 * @author: cww
 * @DateTime: 2019/5/6 19:35
 */

public class LRUCache {
    public static void main(String[] args) {
        LRUByLinkedHashMap<String,Object> lru = new LRUByLinkedHashMap<>(10);
        lru.put("1","一");
        lru.put("2","一");
        lru.put("3","一");
        lru.put("4","一");
        lru.put("5","一");
        lru.put("6","一");
        lru.put("7","一");
        lru.put("8","一");
        lru.put("9","一");
        lru.put("10","一");
        lru.get("5");
        lru.put("11","二");
        System.out.println();
        System.out.println(lru);
    }

}

/**
 * 使用LinkedHashMap实现LRU缓存
 *
 * 1.由于LinkedHashMap是为自动扩容的，当table数组中元素大于Capacity * loadFactor的时候，就会自动进行两倍扩容。但是为了使缓存
 *  大小固定，就需要在初始化的时候传入容量大小和负载因子。为了使得到达设置缓存大小不会进行自动扩容，需要将初始化的大小进行计算再
 *  传入，可以将初始化大小设置为(缓存大小 / loadFactor) + 1，这样就可以在元素数目达到缓存大小时，也不会进行扩容了。这样就解决了
 *  上述第一点问题
 * @param <K>
 * @param <V>
 */
class LRUByLinkedHashMap<K,V> {
    private final int MAX_CACHE_SIZE;
    private final float DEFAULT_LOAD_FACTORY = 0.75F;
    LinkedHashMap<K,V> map;

    public LRUByLinkedHashMap(int cacheSize) {
        this.MAX_CACHE_SIZE = cacheSize;
        //缓存的大小
        int capacity = (int) (Math.ceil(MAX_CACHE_SIZE / DEFAULT_LOAD_FACTORY) + 1);

        /**
         * 第三个参数设置为true，代表LinkedHashMap按访问顺序排序，可作为LRU缓存
         * 第三个参数设置为false，代表按插入顺序排序，可作为FIFO缓存
         */
        map = new LinkedHashMap<K,V>(capacity, DEFAULT_LOAD_FACTORY, true){

            /**
             * 判断当前容量是否超过最大容量，如果超过则需要删除末端元素
             */
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                return size() > MAX_CACHE_SIZE;
            }
        };
    }

    public synchronized void put(K key, V value) {
        map.put(key,value);
    }
    public synchronized V get(K key) {
        return map.get(key);
    }
    public synchronized void remove(K key) {
        map.remove(key);
    }
    public synchronized Set<Map.Entry<K,V>> entrySet() {
        return map.entrySet();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<K,V> entry : map.entrySet()) {
            sb.append(String.format("%s: %s  ", entry.getKey(), entry.getValue()));
        }
        return sb.toString();
    }
}
